// Decompiled on 周六 2月 22 19:31:49 CST 2025 with Zomboid Decompiler v0.1.3 using Vineflower.
package zombie.network;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import zombie.characters.Faction;
import zombie.characters.IsoGameCharacter;
import zombie.characters.IsoPlayer;
import zombie.characters.IsoZombie;
import zombie.characters.SafetySystemManager;
import zombie.commands.PlayerType;
import zombie.core.Core;
import zombie.core.Rand;
import zombie.core.network.ByteBufferWriter;
import zombie.core.raknet.UdpConnection;
import zombie.core.utils.UpdateLimit;
import zombie.core.znet.SteamUtils;
import zombie.debug.DebugLog;
import zombie.iso.IsoMovingObject;
import zombie.iso.IsoUtils;
import zombie.iso.areas.NonPvpZone;
import zombie.network.packets.hit.Character;
import zombie.network.packets.hit.Hit;
import zombie.network.packets.hit.IMovable;
import zombie.network.packets.hit.IPositional;
import zombie.network.packets.hit.Player;
import zombie.network.packets.hit.Zombie;
import zombie.scripting.objects.Recipe;
import zombie.util.StringUtils;
import zombie.util.Type;

public class PacketValidator {
    private static final int SUSPICIOUS_ACTIVITIES_MAX = 4;
    private final UpdateLimit ulSuspiciousActivity = new UpdateLimit(150000L);
    public final HashMap<String, PacketValidator.RecipeDetails> details = new HashMap();
    public final HashMap<String, PacketValidator.RecipeDetails> detailsFromClient = new HashMap();
    private boolean failed = false;
    private static final long USER_LOG_INTERVAL_MS = 1000L;
    private static final int MAX_TYPE_3 = 10;
    private static final int MAX_TYPE_4 = 101;
    private final UdpConnection connection;
    private final UpdateLimit ulTimeMultiplier = new UpdateLimit(this.getTimeMultiplierTimeout());
    private final UpdateLimit ulRecipeChecksumInterval = new UpdateLimit(this.getChecksumInterval());
    private final UpdateLimit ulRecipeChecksumTimeout = new UpdateLimit(this.getChecksumTimeout());
    private int salt;
    private int suspiciousActivityCounter;
    private String suspiciousActivityDescription;

    public PacketValidator(UdpConnection _connection) {
        this.connection = _connection;
        this.suspiciousActivityCounter = 0;
    }

    public void reset() {
        this.salt = Rand.Next(Integer.MAX_VALUE);
    }

    private boolean isReady() {
        IsoPlayer isoPlayer = GameServer.getPlayerByRealUserName(this.connection.username);
        return this.connection.isFullyConnected()
            && this.connection.isConnectionGraceIntervalTimeout()
            && !GameServer.bFastForward
            && isoPlayer != null
            && isoPlayer.isAlive();
    }

    public int getSalt() {
        return this.salt;
    }

    private long getChecksumDelay() {
        return (long)(1000.0 * ServerOptions.getInstance().AntiCheatProtectionType22ThresholdMultiplier.getValue());
    }

    private long getChecksumInterval() {
        return (long)(4000.0 * ServerOptions.getInstance().AntiCheatProtectionType22ThresholdMultiplier.getValue());
    }

    private long getChecksumTimeout() {
        return (long)(10000.0 * ServerOptions.getInstance().AntiCheatProtectionType22ThresholdMultiplier.getValue());
    }

    public void failChecksum() {
        if (ServerOptions.instance.AntiCheatProtectionType21.getValue() && checkUser(this.connection)) {
            DebugLog.Multiplayer.warn("Checksum fail for \"%s\" (Type21)", this.connection.username);
            this.failed = true;
        }

        this.ulRecipeChecksumTimeout.Reset(this.getChecksumDelay());
    }

    public boolean isFailed() {
        return this.failed;
    }

    private void timeoutChecksum() {
        if (this.failed) {
            doKickUser(this.connection, this.getClass().getSimpleName(), "Type21", this.getDescription());
        } else {
            if (ServerOptions.instance.AntiCheatProtectionType22.getValue() && checkUser(this.connection)) {
                doKickUser(this.connection, this.getClass().getSimpleName(), "Type22", null);
            }

            this.ulRecipeChecksumTimeout.Reset(this.getChecksumTimeout());
        }
    }

    public void successChecksum() {
        this.ulRecipeChecksumTimeout.Reset(this.getChecksumTimeout());
    }

    public void sendChecksum(boolean queued, boolean done, boolean _details) {
        this.salt = Rand.Next(Integer.MAX_VALUE);
        GameServer.sendValidatePacket(this.connection, queued, done, _details);
        this.ulRecipeChecksumInterval.Reset(this.getChecksumInterval());
    }

    private long getTimeMultiplierTimeout() {
        return (long)(10000.0 * ServerOptions.getInstance().AntiCheatProtectionType24ThresholdMultiplier.getValue());
    }

    public void failTimeMultiplier(float multiplier) {
        if (ServerOptions.instance.AntiCheatProtectionType23.getValue() && checkUser(this.connection)) {
            doKickUser(this.connection, this.getClass().getSimpleName(), "Type23", String.valueOf(multiplier));
        }

        this.ulTimeMultiplier.Reset(this.getTimeMultiplierTimeout());
    }

    public void timeoutTimeMultiplier() {
        if (ServerOptions.instance.AntiCheatProtectionType24.getValue() && checkUser(this.connection)) {
            doKickUser(this.connection, this.getClass().getSimpleName(), "Type24", null);
        }

        this.ulTimeMultiplier.Reset(this.getTimeMultiplierTimeout());
    }

    public void successTimeMultiplier() {
        this.ulTimeMultiplier.Reset(this.getTimeMultiplierTimeout());
    }

    public void update() {
        if (GameServer.bServer) {
            if (this.ulSuspiciousActivity.Check()) {
                this.updateSuspiciousActivityCounter();
            }

            if (this.isReady()) {
                if (!this.failed && this.ulRecipeChecksumInterval.Check()) {
                    this.sendChecksum(false, false, false);
                }

                if (this.ulRecipeChecksumTimeout.Check()) {
                    this.timeoutChecksum();
                }

                if (this.ulTimeMultiplier.Check()) {
                    this.timeoutTimeMultiplier();
                }
            } else {
                this.ulRecipeChecksumInterval.Reset(this.getChecksumInterval());
                this.ulRecipeChecksumTimeout.Reset(this.getChecksumTimeout());
                this.ulTimeMultiplier.Reset(this.getTimeMultiplierTimeout());
                this.failed = false;
            }
        }
    }

    public static boolean checkPVP(UdpConnection _connection, Character wielder, Character target, String issuedBy) {
        boolean _boolean = checkPVP(wielder.getCharacter(), target.getCharacter())
            || SafetySystemManager.checkUpdateDelay(wielder.getCharacter(), target.getCharacter());
        if (!_boolean && ServerOptions.instance.AntiCheatProtectionType1.getValue() && checkUser(_connection)) {
            doKickUser(_connection, issuedBy, "Type1", null);
        }

        return _boolean;
    }

    public static boolean checkSpeed(UdpConnection _connection, IMovable movable, String issuedBy) {
        float _float = movable.getSpeed();
        double _double = movable.isVehicle() ? ServerOptions.instance.SpeedLimit.getValue() : 10.0;
        boolean _boolean = (double)_float <= _double * ServerOptions.instance.AntiCheatProtectionType2ThresholdMultiplier.getValue();
        if (!_boolean && ServerOptions.instance.AntiCheatProtectionType2.getValue() && checkUser(_connection)) {
            doKickUser(_connection, issuedBy, "Type2", String.valueOf(_float));
        }

        return _boolean;
    }

    public static boolean checkLongDistance(UdpConnection _connection, IPositional wielder, IPositional target, String issuedBy) {
        float _float = IsoUtils.DistanceTo(target.getX(), target.getY(), wielder.getX(), wielder.getY());
        boolean _boolean = (double)_float
            <= (double)(_connection.ReleventRange * 10) * ServerOptions.instance.AntiCheatProtectionType3ThresholdMultiplier.getValue();
        if (!_boolean && ServerOptions.instance.AntiCheatProtectionType3.getValue() && checkUser(_connection)) {
            if (_connection.validator.checkSuspiciousActivity("Type3")) {
                doKickUser(_connection, issuedBy, "Type3", String.valueOf(_float));
            } else {
                doLogUser(_connection, Userlog.UserlogType.SuspiciousActivity, issuedBy, "Type3");
            }
        }

        return _boolean;
    }

    public static boolean checkDamage(UdpConnection _connection, Hit hit, String issuedBy) {
        float _float = hit.getDamage();
        boolean _boolean = (double)_float <= 101.0 * ServerOptions.instance.AntiCheatProtectionType4ThresholdMultiplier.getValue();
        if (!_boolean && ServerOptions.instance.AntiCheatProtectionType4.getValue() && checkUser(_connection)) {
            doKickUser(_connection, issuedBy, "Type4", String.valueOf(_float));
        }

        return _boolean;
    }

    public static boolean checkOwner(UdpConnection _connection, Zombie wielder, String issuedBy) {
        IsoZombie isoZombie = (IsoZombie)wielder.getCharacter();
        UdpConnection udpConnection = isoZombie.authOwner;
        boolean _boolean = udpConnection == _connection && System.currentTimeMillis() - isoZombie.lastChangeOwner > 2000L;
        if (!_boolean && ServerOptions.instance.AntiCheatProtectionType5.getValue() && checkUser(_connection)) {
            if (_connection.validator.checkSuspiciousActivity("Type5")) {
                doKickUser(_connection, issuedBy, "Type5", (String)Optional.ofNullable(udpConnection).map(udpConnectionx -> udpConnectionx.username).orElse(""));
            } else {
                doLogUser(_connection, Userlog.UserlogType.SuspiciousActivity, issuedBy, "Type5");
            }
        }

        return _boolean;
    }

    public static boolean checkTarget(UdpConnection _connection, Player target, String issuedBy) {
        IsoPlayer isoPlayer = target.getPlayer();
        boolean _boolean = Arrays.stream(_connection.players).anyMatch(isoPlayer1 -> isoPlayer1.getOnlineID() == isoPlayer.getOnlineID());
        if (!_boolean && ServerOptions.instance.AntiCheatProtectionType6.getValue() && checkUser(_connection)) {
            doKickUser(_connection, issuedBy, "Type6", isoPlayer.getUsername());
        }

        return _boolean;
    }

    public static boolean checkSafehouseAuth(UdpConnection _connection, String playerName, String issuedBy) {
        boolean _boolean = StringUtils.isNullOrEmpty(playerName) || playerName.equals(_connection.username) || _connection.accessLevel >= 16;
        if (!_boolean && ServerOptions.instance.AntiCheatProtectionType7.getValue() && checkUser(_connection)) {
            doKickUser(_connection, issuedBy, "Type7", playerName);
        }

        return _boolean;
    }

    public static boolean checkShortDistance(UdpConnection _connection, IPositional wielder, IPositional target, String issuedBy) {
        float _float = IsoUtils.DistanceTo(target.getX(), target.getY(), wielder.getX(), wielder.getY());
        boolean _boolean = (double)_float <= 10.0 * ServerOptions.instance.AntiCheatProtectionType3ThresholdMultiplier.getValue();
        if (!_boolean && ServerOptions.instance.AntiCheatProtectionType3.getValue() && checkUser(_connection)) {
            doKickUser(_connection, issuedBy, "Type3", String.valueOf(_float));
        }

        return _boolean;
    }

    private static boolean isUntouchable(UdpConnection udpConnection) {
        return !udpConnection.isFullyConnected()
            || PlayerType.isPrivileged(udpConnection.accessLevel)
            || Arrays.stream(udpConnection.players).filter(Objects::nonNull).anyMatch(IsoGameCharacter::isGodMod);
    }

    public static boolean checkUser(UdpConnection _connection) {
        return doAntiCheatProtection() && !isUntouchable(_connection);
    }

    public boolean checkSuspiciousActivity(String string) {
        if (this.suspiciousActivityCounter <= 4) {
            this.suspiciousActivityCounter++;
            this.suspiciousActivityDescription = String.format("player=\"%s\" type=\"%s\"", this.connection.username, string);
            DebugLog.Multiplayer.noise("SuspiciousActivity increase: counter=%d %s", this.suspiciousActivityCounter, this.suspiciousActivityDescription);
        }

        return this.suspiciousActivityCounter > 4;
    }

    public void updateSuspiciousActivityCounter() {
        if (this.suspiciousActivityCounter > 0) {
            this.suspiciousActivityCounter--;
            DebugLog.Multiplayer.warn("SuspiciousActivity decrease: counter=%d %s", this.suspiciousActivityCounter, this.suspiciousActivityDescription);
        } else {
            this.suspiciousActivityCounter = 0;
        }
    }

    public static void doLogUser(UdpConnection udpConnection, Userlog.UserlogType userlogType, String string0, String string1) {
        long _long = System.currentTimeMillis();
        DebugLog.Multiplayer.warn("Log: player=\"%s\" type=\"%s\" issuer=\"%s\"", udpConnection.username, string0, string1);
        if (_long > udpConnection.lastUnauthorizedPacket) {
            udpConnection.lastUnauthorizedPacket = _long + 1000L;
            ServerWorldDatabase.instance.addUserlog(udpConnection.username, userlogType, string0, "AntiCheat" + string1, 1);
        }
    }

    public static void doKickUser(UdpConnection udpConnection, String string0, String string1, String string2) {
        ServerWorldDatabase.instance.addUserlog(udpConnection.username, Userlog.UserlogType.Kicked, string0, "AntiCheat" + string1, 1);
        DebugLog.Multiplayer.warn("Kick: player=\"%s\" type=\"%s\" issuer=\"%s\" description=\"%s\"", udpConnection.username, string0, string1, string2);
        GameServer.kick(udpConnection, "UI_Policy_Kick", string1);
        udpConnection.forceDisconnect(string0);
        GameServer.addDisconnect(udpConnection);
    }

    public static void doBanUser(UdpConnection _connection, String issuedBy, String reason) throws Exception {
        ServerWorldDatabase.instance.addUserlog(_connection.username, Userlog.UserlogType.Banned, issuedBy, "AntiCheat" + reason, 1);
        DebugLog.Multiplayer.warn("Ban: player=\"%s\" type=\"%s\" issuer=\"%s\"", _connection.username, issuedBy, reason);
        ServerWorldDatabase.instance.banUser(_connection.username, true);
        if (SteamUtils.isSteamModeEnabled()) {
            String string = SteamUtils.convertSteamIDToString(_connection.steamID);
            ServerWorldDatabase.instance.banSteamID(string, issuedBy, true);
        } else {
            ServerWorldDatabase.instance.banIp(_connection.ip, _connection.username, issuedBy, true);
        }

        GameServer.kick(_connection, "UI_Policy_Ban", reason);
        _connection.forceDisconnect(issuedBy);
        GameServer.addDisconnect(_connection);
    }

    private static boolean checkPVP(IsoGameCharacter isoGameCharacter, IsoMovingObject isoMovingObject) {
        IsoPlayer isoPlayer0 = Type.tryCastTo(isoGameCharacter, IsoPlayer.class);
        IsoPlayer isoPlayer1 = Type.tryCastTo(isoMovingObject, IsoPlayer.class);
        if (isoPlayer1 != null) {
            if (isoPlayer1.isGodMod()
                || !ServerOptions.instance.PVP.getValue()
                || ServerOptions.instance.SafetySystem.getValue()
                    && isoGameCharacter.getSafety().isEnabled()
                    && ((IsoGameCharacter)isoMovingObject).getSafety().isEnabled()) {
                return false;
            }

            if (NonPvpZone.getNonPvpZone((int)isoMovingObject.getX(), (int)isoMovingObject.getY()) != null) {
                return false;
            }

            if (isoPlayer0 != null && NonPvpZone.getNonPvpZone((int)isoGameCharacter.getX(), (int)isoGameCharacter.getY()) != null) {
                return false;
            }

            if (isoPlayer0 != null && !isoPlayer0.factionPvp && !isoPlayer1.factionPvp) {
                Faction faction0 = Faction.getPlayerFaction(isoPlayer0);
                Faction faction1 = Faction.getPlayerFaction(isoPlayer1);
                if (faction1 != null && faction0 == faction1) {
                    return false;
                }
            }
        }

        return true;
    }

    public static boolean doAntiCheatProtection() {
        return !GameServer.bCoop && (!Core.bDebug || zombie.SystemDisabler.doKickInDebug);
    }

    public String getDescription() {
        StringBuilder stringBuilder = new StringBuilder("Recipes CRC details");
        if (GameServer.bServer) {
            Set set = (Set)this.details
                .entrySet()
                .stream()
                .filter(
                    entry -> this.detailsFromClient.get(entry.getKey()) != null
                            && ((PacketValidator.RecipeDetails)this.detailsFromClient.get(entry.getKey())).crc
                                == ((PacketValidator.RecipeDetails)entry.getValue()).crc
                )
                .map(Entry::getKey)
                .collect(Collectors.toSet());
            set.forEach(string -> {
                this.detailsFromClient.remove(string);
                this.details.remove(string);
            });
            stringBuilder.append("\nServer start size=").append(this.details.size());
            this.details.values().forEach(recipeDetails -> stringBuilder.append(recipeDetails.getDescription()));
            stringBuilder.append("\nServer end\nClient start size=").append(this.detailsFromClient.size());
            this.detailsFromClient.values().forEach(recipeDetails -> stringBuilder.append(recipeDetails.getDescription()));
            stringBuilder.append("\nClient end");
        }

        return stringBuilder.toString();
    }

    public static enum CheckState {
        None,
        Sent,
        Success;
    }

    public static class RecipeDetails {
        private final String name;
        private final long crc;
        private final int timeToMake;
        private final ArrayList<PacketValidator.RecipeDetails.Skill> skills = new ArrayList();
        private final ArrayList<String> items = new ArrayList();
        private final String type;
        private final String module;
        private final int count;

        public long getCRC() {
            return this.crc;
        }

        public RecipeDetails(
            String _name,
            long _crc,
            int _timeToMake,
            ArrayList<Recipe.RequiredSkill> _skills,
            ArrayList<Recipe.Source> sources,
            String _type,
            String _module,
            int _count
        ) {
            this.name = _name;
            this.crc = _crc;
            this.timeToMake = _timeToMake;
            this.type = _type;
            this.module = _module;
            this.count = _count;
            if (_skills != null) {
                for (Recipe.RequiredSkill requiredSkill : _skills) {
                    this.skills.add(new PacketValidator.RecipeDetails.Skill(requiredSkill.getPerk().getName(), requiredSkill.getLevel()));
                }
            }

            for (Recipe.Source source : sources) {
                this.items.addAll(source.getItems());
            }
        }

        public RecipeDetails(ByteBuffer b) {
            this.name = zombie.GameWindow.ReadString(b);
            this.crc = b.getLong();
            this.timeToMake = b.getInt();
            this.type = zombie.GameWindow.ReadString(b);
            this.module = zombie.GameWindow.ReadString(b);
            this.count = b.getInt();
            int int0 = b.getInt();

            for (int int1 = 0; int1 < int0; int1++) {
                this.items.add(zombie.GameWindow.ReadString(b));
            }

            int int2 = b.getInt();

            for (int int3 = 0; int3 < int2; int3++) {
                this.skills.add(new PacketValidator.RecipeDetails.Skill(zombie.GameWindow.ReadString(b), b.getInt()));
            }
        }

        public void write(ByteBufferWriter b) {
            b.putUTF(this.name);
            b.putLong(this.crc);
            b.putInt(this.timeToMake);
            b.putUTF(this.type);
            b.putUTF(this.module);
            b.putInt(this.count);
            b.putInt(this.items.size());

            for (String string : this.items) {
                b.putUTF(string);
            }

            b.putInt(this.skills.size());

            for (PacketValidator.RecipeDetails.Skill skill : this.skills) {
                b.putUTF(skill.name);
                b.putInt(skill.value);
            }
        }

        public String getDescription() {
            return "\n\tRecipe: name=\""
                + this.name
                + "\" crc="
                + this.crc
                + " time="
                + this.timeToMake
                + " type=\""
                + this.type
                + "\" module=\""
                + this.module
                + "\" count="
                + this.count
                + " items=["
                + String.join(",", this.items)
                + "] skills=["
                + (String)this.skills.stream().map(skill -> "\"" + skill.name + "\": " + skill.value).collect(Collectors.joining(","))
                + "]";
        }

        public static class Skill {
            private final String name;
            private final int value;

            public Skill(String _name, int _value) {
                this.name = _name;
                this.value = _value;
            }
        }
    }
}
